Skip to content

Progressive rendering with DOM morphing for generative UI#40

Merged
GeneralJerel merged 2 commits intomainfrom
fix/progressive-rendering-33
Mar 24, 2026
Merged

Progressive rendering with DOM morphing for generative UI#40
GeneralJerel merged 2 commits intomainfrom
fix/progressive-rendering-33

Conversation

@GeneralJerel
Copy link
Collaborator

Summary

Closes #33

  • WidgetRenderer: Replace innerHTML with idiomorph DOM morphing to eliminate flickering during streaming. Existing DOM nodes are preserved across updates — only diffs are applied. New elements fade in via .morph-enter animation; first render uses staggered entrance.
  • BarChart / PieChart: Enable Recharts built-in entrance animations (800ms ease-out)
  • MeetingTimePicker: Add staggered fade-in animation to time slot buttons (80ms delay between each)

Test plan

  • Send a chat message that triggers WidgetRenderer (e.g. "draw a diagram of how DNS works") — content should appear progressively without flickering
  • Verify "Save as Template" button still appears after streaming completes
  • Trigger a bar chart — bars should animate in
  • Trigger a pie chart — slices should animate in with slight delay
  • Trigger MeetingTimePicker — time slots should stagger in one by one

🤖 Generated with Claude Code

)

Replace innerHTML with idiomorph DOM morphing in WidgetRenderer to
eliminate flickering during streaming. Existing DOM nodes are preserved
across updates — only diffs are applied. New elements fade in via
.morph-enter animation; first render uses staggered entrance.

Also enable Recharts entrance animations on bar/pie charts and add
staggered fade-in to MeetingTimePicker time slots.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Collaborator Author

@GeneralJerel GeneralJerel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Overall: Good approach — replacing innerHTML with idiomorph DOM morphing is the right call to fix streaming flicker. The changes are focused and the fallback path is sensible. A few issues worth addressing:

Issues

1. Inlined minified library is a maintenance burdenwidget-renderer.tsx:334-337

The IDIOMORPH_JS constant is ~4KB of minified code inlined as a string literal. This makes the file harder to read, impossible to diff on library updates, and there's no way to verify it matches the claimed v0.3.0. Consider installing idiomorph as a dependency and bundling it, or at minimum adding a script that regenerates this constant from the npm package so it can be verified/updated.

2. morph-enter class is never removedwidget-renderer.tsx:390-394

The beforeNodeAdded callback adds morph-enter to every new element node, but nothing ever removes it. Since the animation uses both fill mode, the visual result is fine, but:

  • The class accumulates on every element across morphs
  • If the CSS were ever changed (e.g., removing both), stale classes would cause bugs
  • It adds noise when inspecting the DOM

Consider removing the class after the animation completes (e.g., via an animationend listener).

3. Inline <style> in MeetingTimePicker JSXmeeting-time-picker.tsx:49-54

Injecting a <style> tag directly in the component body means it's re-inserted on every render. This keyframe is generic enough to live in a shared CSS file or as a Tailwind @keyframes definition. It also duplicates the concept already in widget-renderer.tsx's fadeSlideIn.

4. No data-has-content reset pathwidget-renderer.tsx:377-382

Once data-has-content is set, the widget can never get the staggered entrance animation again (e.g., if the content is cleared and a new streaming session starts in the same iframe). If the iframe is always recycled between sessions this is fine, but worth confirming.

Minor / Nits

  • Chart animation props look good. The 200ms animationBegin delay on pie gives a nice sequenced feel.
  • The prefers-reduced-motion media query correctly respects accessibility — nice.
  • The initial-render class removal timeout (800ms) matches the animation budget well.

Verdict

The core approach is solid. Issues #1 and #2 are worth fixing before merge; #3 and #4 are minor improvements that could be follow-ups.

… animations

Extract inlined idiomorph minified code to dedicated module with update
instructions. Add animationend listener to remove morph-enter class after
animation completes. Move MeetingTimePicker fadeSlideUp keyframes from
inline <style> to globals.css. Reset data-has-content on empty content so
new streaming sessions get the staggered entrance animation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@GeneralJerel GeneralJerel merged commit 6a00ff7 into main Mar 24, 2026
9 of 10 checks passed
@GeneralJerel GeneralJerel deleted the fix/progressive-rendering-33 branch March 24, 2026 15:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Progressive rendering for generative UI components

1 participant